﻿using System;
using System.Collections;
using System.Collections.Generic;
using umbraco.cms.businesslogic.datatype;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.web;
using System.Linq;

namespace umbraco.DataServices.Linq {

    public class InheritanceBuilder {

        private IEnumerable<DocumentType> _documentTypes;
        private IEnumerable<PropertyType> _properties;

        public IDocumentTypeSignatureCollection Create() {
            //Get Metadata From Database And Keep In Memory
            _documentTypes = DocumentType.GetAllAsList();
            _properties = PropertyType.GetAll();

            var list = new DocumentTypeSignatureCollection();
            var baseDocumentTypes = _documentTypes.Where(p => p.MasterContentType == default(int));
            foreach (var item in baseDocumentTypes) {
                var documentType = FillDocumentType(item, null);
                FindInheritedTypes(list, documentType);
                list.Add(documentType);
            }
            return list;
        }

        private void FindInheritedTypes(IList index, DocumentTypeSignature parentDocumentType) {
            var inheritedDocumentItems = _documentTypes.Where(p => p.MasterContentType == parentDocumentType.Id);
            foreach (var item in inheritedDocumentItems) {
                var documentType = FillDocumentType(item, parentDocumentType);
                index.Add(documentType);
                FindInheritedTypes(index, documentType);
            }
        }

        private List<PropertyTypeSignature> FindProperties(DocumentTypeSignature parentDocumentType) {
            return _properties.Where(p => p.ContentTypeId == parentDocumentType.Id).Select(FillProperty).ToList();
        }

        private DocumentTypeSignature FillDocumentType(DocumentType docType, DocumentTypeSignature parent) {
            if (docType == null)
                throw new ArgumentNullException("docType");
            var item = new DocumentTypeSignature(parent) { Alias = docType.Alias, Id = docType.Id };
            item.Properties = FindProperties(item);
            return item;
        }

        private static PropertyTypeSignature FillProperty(PropertyType propertyType) {
            var dataType = (BaseDataType)propertyType.DataTypeDefinition.DataType;
            var returnProperty = typeof(string);
            if (dataType.DBType == DBTypes.Date)
                returnProperty = typeof(DateTime);
            if (dataType.DBType == DBTypes.Integer)
                returnProperty = typeof(int);
            return new PropertyTypeSignature(propertyType.Id, propertyType.Alias, propertyType.Mandatory, returnProperty);
        }
    }

    public class DocumentTypeSignature {

        public DocumentTypeSignature(DocumentTypeSignature parent) {
            Properties = new List<PropertyTypeSignature>();
            BaseDocumentType = parent;
        }

        public DocumentTypeSignature() {
            Properties = new List<PropertyTypeSignature>();
        }

        public int Id { get; set; }
        public string Alias { get; set; }

        public DocumentTypeSignature BaseDocumentType { get; private set; }
        public List<PropertyTypeSignature> Properties { get; set; }

        public override string ToString() {
            return Id + "." + Alias;
        }

    }

    public interface IDocumentTypeSignatureCollection : IEnumerable<DocumentTypeSignature> {
        DocumentTypeSignature GetById(int id);
        DocumentTypeSignature GetByAlias(string alias);
        IEnumerable<DocumentTypeSignature> GetDescendantDocumentTypes(string documentTypeAlias);
        IEnumerable<DocumentTypeSignature> GetDescendantDocumentTypes(DocumentTypeSignature documentType);
        IEnumerable<DocumentTypeSignature> GetParentDocumentTypes(string documentTypeAlias);
        IEnumerable<DocumentTypeSignature> GetParentDocumentTypes(DocumentTypeSignature documentType);
        IEnumerable<DocumentTypeSignature> GetChildDocumentTypes(DocumentTypeSignature documentType);
        IEnumerable<DocumentTypeSignature> GetChildDocumentTypes(string documentTypeAlias);
    }

    public class DocumentTypeSignatureCollection : List<DocumentTypeSignature>, IDocumentTypeSignatureCollection {

        public DocumentTypeSignature GetById(int id) {
            return this.FirstOrDefault(p => p.Id == id);
        }

        public DocumentTypeSignature GetByAlias(string alias) {
            return this.FirstOrDefault(p => p.Alias == alias);
        }

        public IEnumerable<DocumentTypeSignature> GetDescendantDocumentTypes(string documentTypeAlias) {
            var item = GetByAlias(documentTypeAlias);
            if (item == null)
                return new DocumentTypeSignature[] {};
            return GetDescendantDocumentTypes(item);
        }

        /// <summary>
        /// Used To Find Document Types That Inherit From A Single Document Type
        /// </summary>
        public IEnumerable<DocumentTypeSignature> GetDescendantDocumentTypes(DocumentTypeSignature documentType) {
            return GetDescendantDocumentTypes(new List<DocumentTypeSignature>(), documentType);
        }

        /// <summary>
        /// Used To Find Document Types That Inherit From A Single Document Type
        /// </summary>
        private IEnumerable<DocumentTypeSignature> GetDescendantDocumentTypes(ICollection<DocumentTypeSignature> list, DocumentTypeSignature documentType)
        {
            var allItems = this.Where(p => p.BaseDocumentType == documentType);
            foreach (var item in allItems) {
                list.Add(item);
                GetDescendantDocumentTypes(list, item);
            }
            return list;
        }

        public IEnumerable<DocumentTypeSignature> GetParentDocumentTypes(string documentTypeAlias) {
            var item = GetByAlias(documentTypeAlias);
            if (item == null)
                return new DocumentTypeSignature[] { };
            return GetParentDocumentTypes(item);
        }

        /// <summary>
        /// Used To Find Properties In Parent Document Types
        /// </summary>
        public IEnumerable<DocumentTypeSignature> GetParentDocumentTypes(DocumentTypeSignature documentType) {
            var parent = documentType.BaseDocumentType;
            while (parent != null) {
                yield return parent;
                parent = parent.BaseDocumentType;
            }
        }

        public IEnumerable<DocumentTypeSignature> GetChildDocumentTypes(DocumentTypeSignature documentType) {
            return this.Where(p => p.BaseDocumentType == documentType);
        }

        public IEnumerable<DocumentTypeSignature> GetChildDocumentTypes(string documentTypeAlias) {
            var item = GetByAlias(documentTypeAlias);
            if (item == null)
                return new DocumentTypeSignature[] { };
            return GetChildDocumentTypes(item);
        }
    }

    public class PropertyTypeSignature {

        public PropertyTypeSignature(int id, string alias, bool required, Type type) {
            Id = id;
            Alias = alias;
            Required = required;
            Type = type;
        }

        public int Id { get; private set; }
        public string Alias { get; private set; }
        public bool Required { get; private set; }
        public Type Type { get; private set; }

        public override string ToString() {
            return Id + "." + Alias;
        }

    }

}
